package com.example.demo.aspect;

import com.example.demo.annotation.RepeatForm;
import com.example.demo.common.enums.CustomizeEnum;
import com.example.demo.common.handler.CustomException;
import com.example.demo.entity.User;
import com.example.demo.service.impl.UserServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * @Author: ak
 * @Date: 2020/4/13 9:28
 */
@Aspect
@Component
@Slf4j
public class RepeatFormAspect {
    @Autowired
    RedisTemplate<String, Object> redisTemplate;
    @Autowired
    UserServiceImpl userService;
    /**
     * redis的key，维护一个用户冻结列表
     */
    private static final String FREEZE_USER_LIST = "freezeUserList";
    /**
     * 最大请求次数
     */
    private static final int MAX_REQUEST_COUNT = 5;
    /**
     * 步长
     */
    private static final long STEP_SIZE = 1;
    /**
     * 请求次数初始值
     */
    private static final int INIT_VALUE = 1;
    /**
     * 冻结时长，设置10分钟
     */
    private static final long FREEZE_TIME = 60 * 10;
    /**
     * 请求限制的时长
     */
    private static final long LIMIT_TIME = 10;

    @Pointcut("@annotation(repeatForm)")
    public void pointCut(RepeatForm repeatForm) {
    }

    @Around(value = "pointCut(repeatForm)", argNames = "pjp,repeatForm")
    public Object around(ProceedingJoinPoint pjp, RepeatForm repeatForm) throws CustomException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        try {
            initFreezeUserList();
            User user = (User) SecurityUtils.getSubject().getPrincipal();
            if (user == null) {
                log.error("用户未登录!");
                request.setAttribute("loginMsg", "用户未登录");
                request.getRequestDispatcher("/login").forward(request, response);
                return null;
            }
            Object[] args = pjp.getArgs();
            String methodName = pjp.getSignature().getName();
            String redisKey = getRedisKey(user.getUUsername(), args, methodName);
            log.debug("用户{}访问{}路由", user.getUUsername(), methodName);
            boolean unFreeze = isUnFreeze(user.getUUsername());
            if (!unFreeze) {
                log.info("用户{}冻结中!请十分钟之后再试!", user.getUUsername());
                request.setAttribute("msg", "用户冻结中!请十分钟之后再试!");
                request.getRequestDispatcher("index").forward(request, response);
                return null;
            }
            putOrUpdateCache(redisKey);
            return pjp.proceed();
        } catch (Throwable throwable) {
            throw new CustomException("路由注解发生错误！", 500);
        }
    }

    /**
     * redis的key生成策略
     */
    private String getRedisKey(String username, Object[] args, String methodName) {
        StringBuilder stringBuilder = new StringBuilder();
        if (args.length > 0) {
            stringBuilder.append(args[0].toString().split("@")[0]);
            for (int i = 1; i < args.length; i++) {
                String arg = args[i].toString().split("@")[0];
                stringBuilder.append(",").append(arg);
            }
        }
        return username + ":" + methodName + "{" + stringBuilder + "}";
    }

    /**
     * 更新缓存或将请求信息存入缓存
     *
     * @param redisKey 用户请求信息的key
     */
    private void putOrUpdateCache(String redisKey) {
        Integer count = (Integer) redisTemplate.opsForValue().get(redisKey);
        if (count == null) {
            //限制key的时长为10秒，10秒内请求过5次则冻结用户
            log.info("第一次访问");
            redisTemplate.opsForValue().set(redisKey, INIT_VALUE, LIMIT_TIME, TimeUnit.SECONDS);
        } else if (count > MAX_REQUEST_COUNT) {
            //同一请求超过5次，冻结该用户
            String username = redisKey.split(":")[0];
            freezeUser(username);
        } else {
            redisTemplate.opsForValue().increment(redisKey, STEP_SIZE);
            log.info("当前访问次数：{}", count);
        }
    }

    private void freezeUser(String username) {
        long currentTime = System.currentTimeMillis() / 1000;
        redisTemplate.opsForHash().put(FREEZE_USER_LIST, username, currentTime);
    }

    private boolean isUnFreeze(String username) {
        Object flag = redisTemplate.opsForHash().get(FREEZE_USER_LIST, username);
        //如果flag为空，则冻结列表无此用户
        if (flag == null) {
            return true;
        }
        long startTime = Long.parseLong(String.valueOf(flag));
        long currentTime = System.currentTimeMillis() / 1000;
        return currentTime - startTime > FREEZE_TIME;
    }

    private void initFreezeUserList() {
        //初始化用户冻结列表
        if (redisTemplate.opsForValue().get(FREEZE_USER_LIST) == null) {
            redisTemplate.opsForHash().put(FREEZE_USER_LIST, "", "");
        }
    }

}
